package csm.uttils;

/**
 * @author Carlos S. Moreno
 */
public class Generator {

    private long seed;
    private long current;
    private int generatedNumbersCount;
    private boolean normalFlag;  // Used in normal distribution

    /**
     * Constructor
     */
    public Generator() {
        this(324132521);
    }

    /**
     * Constructor
     * RND = (a+bxs)%m
     * @param s Seed
     */
    public Generator(long s) {
        seed = s;
    }

    

    /*
     * Consistent Multiplicative
     * RNDi+1 = (a*RND+b)%m
     */
    public long generateConsistentMultiplicativeDistribution(double a, double b,
            double m, double rnd) {
        long numAl = (long)(((a * rnd) + b) % m);
        current = numAl;
        generatedNumbersCount++;
        return numAl;
    }
    
    /*
     * Consistent Multiplicative
     * RNDi+1 = (a*RND+b)%m
     */
    public long generateConsistentMultiplicativeDistribution(double a, double b,
            double m) {
        return generateConsistentMultiplicativeDistribution(a, b, m, current);
    }
    
    

    /**
     * Returns a randon number between 0 & 1.
     * @return Random double number
     */
    public double getRND() {
        int m = 11;
        int a = 1009;
        int b = 7;
        long num = generateConsistentMultiplicativeDistribution(a, b, m, seed);
        int cap = m +1;

        return (double) num / (double) cap;
    }

    /**
     * Get a number from a uniform distribution
     * @param a lowet limit
     * @param b highest limit
     * @return a random double between a and b.
     */
    public double generateUniformDistribution(double a, double b, double rnd) {
        return (rnd * (b - a) + a);
    }
    
    /**
     * Get a number from a uniform distribution
     * @param a lowet limit
     * @param b highest limit
     * @return a random double between a and b.
     */
    public double generateUniformDistribution(double a, double b) {
        return generateUniformDistribution(a, b, getRND());
    }

    /**
     * Get a number from a exponential distribution
     * @param lambda
     * @return random double by exponential distribution
     */
    public double generateExponentialDistribution(double lambda, double rnd) {
        double num = 1 - rnd;

        num = Math.log(num);
        num = num / (-lambda);

        return num;

    }
    
    /**
     * Get a number from a exponential distribution
     * @param lambda
     * @return random double by exponential distribution
     */
    public double generateExponentialDistribution(double lambda) {
        return generateExponentialDistribution(lambda, getRND());

    }

    /**
     * Get a random number from a poisson distribution
     * @param lambda
     * @return 
     */
    public int generatePoissonDistribution(int lambda) {

        double a = Math.exp(-lambda);
        double b = 1;
        int i = 0;
        double x = 0;

        do {
            x = Math.random();
            b = b * x;
            i++;
        } while (b > a);

        return i;//Checks if it is not -1 at at the begining

    }

    /**
     * Get a random number from a Normal Distribution
     * @return 
     */
    public double generateNormalDistribution(double average, double variance,
            double rnd1, double rnd2) {
        double z;
        double ri, rj;

        ri = rnd1;
        rj = rnd2;

        if (normalFlag) {
            z = (Math.sqrt((-2) * Math.log(ri))) * (Math.cos(2 * Math.PI * (rj)));
            normalFlag = false;
        } else {
            z = (Math.sqrt((-2) * Math.log(ri))) * (Math.cos(2 * Math.PI * (rj)));
            normalFlag = true;

        }

        double x = average + (variance) * z;

        return x;
    }
    
    /**
     * Get a random number from a Normal Distribution
     * @return 
     */
    public double generateNormalDistribution(double average, double variance) {
        return generateNormalDistribution(average, variance,getRND(), getRND());
    }
}
